package com.Jshop.search.Service;

import com.Jshop.search.client.CategoryClient;
import com.Jshop.search.client.SpecificationClient;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.Jshop.search.client.BrandClient;
import com.Jshop.search.client.GoodsClient;
import com.Jshop.search.pojo.Goods;
import com.Jshop.search.pojo.SearchRequest;
import com.Jshop.search.pojo.SearchResult;
import com.Jshop.search.reponsitory.GoodsRepository;
import com.leyou.item.pojo.*;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.*;
/**
 * 从ES中查询数据并构建SearchResult
 * */
@Service
public class SearchService {
    //查询数据 --> Goods

    @Autowired
    private BrandClient brandClient;
    @Autowired
    private CategoryClient categoryClient;
    @Autowired
    private GoodsClient goodsClient;
    @Autowired
    private SpecificationClient specificationClient;
    @Autowired
    private GoodsRepository goodsRepository;


    //JSON与实体类互转的工具类
    //jackson提供
    private ObjectMapper MAPPER = new ObjectMapper();



    //构建ES索引单个对象Goods
    public Goods buildGoods(Spu spu) throws IOException {
        //打包传给索引
        Goods goods = new Goods();
        //品牌信息
        Brand brand = this.brandClient.queryBrandById(spu.getBrandId());

        //根据分类id查询三级分类名称
        List<String> names = this.categoryClient.queryNameByIds
                (Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()));

        //查询spu下所有所有sku
        List<Sku> skus = this.goodsClient.querySkuBySpuId(spu.getId());

        //定义价格数组 该商品有几种不同的价格（sku）
        List<Long> prices = new ArrayList<>();

        //每种sku都是map集合类型 要该商品所有的sku
        List<Map<String,Object>> skuMapList = new ArrayList<>();

        //遍历skus，获取所有价格
        skus.forEach(sku -> {
            //sku的价格入集合
            prices.add(sku.getPrice());
            Map<String,Object> skuMap = new HashMap<>();
            skuMap.put("id",sku.getId());
            skuMap.put("title",sku.getTitle());
            skuMap.put("price",sku.getPrice());
            //可能会有多张图片 只拿第一张
            skuMap.put("image", StringUtils.isNotBlank(sku.getImages()) ?
                    StringUtils.split(sku.getImages(),",")[0] : "");
            skuMapList.add(skuMap);
        });


        //查询出所有的搜索规格Params

        //只要该商品的所有可搜索属性
        List<SpecParam> params = this.specificationClient.queryParams(null, spu.getCid3(), null, true);

        //查询spuDetail，获取规格参数"值" 值分通用与特殊，特殊与sku相关
        SpuDetail spuDetail = this.goodsClient.querySpuDetailById(spu.getId());
        //获取通用的规格参数

        //      {"param_id":"value", ... }
        //       --> Long,Object ...
        //{"1":"华为（HUAWEI）","2":"华为麦芒5（HUAWEI MLA-AL10）","3":2016.0,"5":160,"6":"其它","7":"Android","8":"骁龙（Snapdragon)","9":"骁龙625（MSM8953）","10":"八核","11":2.0,"14":5.5,"15":"1920*1080(FHD)","16":800.0,"17":1600.0,"18":3340.0}
        Map<Long,Object> genericSpecMap = MAPPER.readValue(spuDetail.getGenericSpec(), new TypeReference<Map<Long, Object>>() {
        });


        //获取特殊的规格参数
        //    {"4":["白色","金色","玫瑰金"],"12":["3GB"],"13":["16GB"]}
        // --> Long,List ...
        Map<Long,List<Object>>  specialSpecMap = MAPPER.readValue(spuDetail.getSpecialSpec(), new TypeReference<Map<Long, List<Object>>>() {
        });

        //定义map接收{规格参数名，规格参数值}
        //将来可以被用来聚合搜索
        Map<String,Object> paramMap = new HashMap<>();
        params.forEach(param -> {
             //判断是否通用
            if (param.getGeneric()){
                //获取通用规格参数值
                String value = genericSpecMap.get(param.getId()).toString();
                //判断是否是数值类型
                if (param.getNumeric()){
                    //如果是数值的话，判断该数值落在哪个区间
                    //value = 0-500 500-1000...
                    value = chooseSegment(value, param);
                }
                //把值放在结果集中
                paramMap.put(param.getName(),value);
            }else {
                paramMap.put(param.getName(),specialSpecMap.get(param.getId()));
            }
        });

        // 设置参数
        goods.setId(spu.getId());
        goods.setCid1(spu.getCid1());
        goods.setCid2(spu.getCid2());
        goods.setCid3(spu.getCid3());
        goods.setBrandId(spu.getBrandId());
        goods.setCreateTime(spu.getCreateTime());
        goods.setSubTitle(spu.getSubTitle());
        goods.setAll(spu.getTitle() + brand.getName() + StringUtils.join(names, " "));
        goods.setPrice(prices);
        goods.setSkus(MAPPER.writeValueAsString(skuMapList));
        goods.setSpecs(paramMap);

        return goods;
    }

    //返回价格区间
    private String chooseSegment(String value, SpecParam p) {
        double val = NumberUtils.toDouble(value);
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = NumberUtils.toDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if(segs.length == 2){
                end = NumberUtils.toDouble(segs[1]);
            }
            // 判断是否在范围内
            if(val >= begin && val < end){
                if(segs.length == 1){
                    result = segs[0] + p.getUnit() + "以上";
                }else if(begin == 0){
                    result = segs[1] + p.getUnit() + "以下";
                }else{
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }


    //从ES从查找数据 返回
    public SearchResult search(SearchRequest request){
        //获取搜索关键词
        String key = request.getKey();
        //判断是否有搜索条件，如果没有，直接返回null,不允许搜索全部商品
        if (StringUtils.isBlank(key)){
            return null;
        }
        //构建查询条件
        NativeSearchQueryBuilder queryBuilder = new
                NativeSearchQueryBuilder();

        //对key进行全文检索查询
        //MatchQueryBuilder basicQuery = QueryBuilders.matchQuery("all", key).operator(Operator.AND);
        BoolQueryBuilder boolQueryBuilder = buildBooleanQueryBuilder(request);
        queryBuilder.withQuery(boolQueryBuilder);

        //过滤，只需要id、skus、subTitle
        //即展示的每台手机
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id","skus","subTitle"},null));

        //分页参数
        Integer page = request.getPage();
        Integer size = request.getSize();
        queryBuilder.withPageable(PageRequest.of(page - 1,size));

        //搜索条件的聚合

        //分类、品牌聚合
        //展示在条件筛选模块
        queryBuilder.addAggregation(AggregationBuilders.terms("categories").field("cid3"));
        queryBuilder.addAggregation(AggregationBuilders.terms("brands").field("brandId"));


        //查询、获取结果
        //由于返回结果有聚合，需要用Page的子接口
        AggregatedPage<Goods> pageInfo = (AggregatedPage<Goods>) this.goodsRepository.search(queryBuilder.build());

        //解析聚合结果集

        //分类
        Aggregation categories = pageInfo.getAggregation("categories");

        List<Map<String, Object>> categoryAggResult = getCategoryAggResult(categories);


        //品牌
        Aggregation brands = pageInfo.getAggregation("brands");
        List<Brand> brandAggResult = getBrandAggResult(brands);


        //规格参数
        //判断分类聚合的结果集大小，等于一才聚合规格参数
        List<Map<String,Object>> specs = null;
        if (categoryAggResult.size() == 1){
             specs = getParamAggResult((Long) categoryAggResult.get(0).get("id"), boolQueryBuilder);
        }

        //封装结果并返回
        return new SearchResult(pageInfo.getContent(),pageInfo.getTotalElements(),pageInfo.getTotalPages(),categoryAggResult,brandAggResult,specs);
    }

    //解析品牌聚合结果集
    //
    private List<Brand> getBrandAggResult(Aggregation aggregation){
        //处理聚合结果集
        //获取桶buckets
        List<LongTerms.Bucket> buckets = ((LongTerms) aggregation).getBuckets();
        //定义一个品牌集合，搜集所有品牌对象
        List<Brand> brands = new ArrayList<>();
        //解析所有id桶，查询品牌
        buckets.forEach(bucket -> {
            Brand brand = this.brandClient.queryBrandById(bucket.getKeyAsNumber().longValue());
            brands.add(brand);
        });
        return brands;
    }


    /**
     * 解析分类聚合
     * 用于前台  筛选模块 展示
     * */
    private List<Map<String,Object>> getCategoryAggResult(Aggregation aggregation){
        //处理聚合结果集

        //获取桶
        //bucket = 76.
        List<LongTerms.Bucket> buckets = ((LongTerms) aggregation).getBuckets();

        // map结构大概是 { names:[cid1name ... ] id : [cid_1...] ... }
        // Object是数组
        //不清除就看下前端怎么解析
        List<Map<String,Object>> categories = new ArrayList<>();
        List<Long> cids  = new ArrayList<>();

        buckets.forEach(bucket -> {
            cids.add(bucket.getKeyAsNumber().longValue());
        });

        //所有的三级分类名称
        List<String> names = this.categoryClient.queryNameByIds(cids);
        for (int i = 0; i < cids.size(); i++) {
            Map<String,Object> map = new HashMap<>();
            //设置所有的三级分类id
            map.put("id",cids.get(i));
            map.put("name",names.get(i));
            categories.add(map);
        }

        return categories;
        //categories = [{name = 手机, id = 76} ... 多个分类]
    }

    /**
     * 解析规格参数聚合
     * 用于前台  筛选模块 展示
     * */
    private List<Map<String,Object>> getParamAggResult(Long id, QueryBuilder basicQuery){
        //创建自定义查询构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //基于基本的查询条件，聚合规格参数
        queryBuilder.withQuery(basicQuery);
        //查询要聚合的规格参数
        //以cid查询规格参数 isSearch -> true
        List<SpecParam> params = this.specificationClient.queryParams(null, id, null, true);

        //聚合
        //因为规格参数保存时不做分词，因此其名称会自动带上一个.keyword后缀
        params.forEach(param->{
            queryBuilder.addAggregation(AggregationBuilders.terms(param.getName()).field("specs." + param.getName() + ".keyword"));
        });
        //只需要聚合结果集，不需要查询结果集
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{},null));
        //执行聚合查询
        AggregatedPage<Goods> goodsPage = (AggregatedPage<Goods>) this.goodsRepository.search(queryBuilder.build());

        //收集聚合结果集
        List<Map<String,Object>> paramMapList = new ArrayList<>();

        //解析聚合查询的结果集
        //注意这里有多个聚合 每个 参数-参数值都是一个小聚合 所有参数作为key
        Map<String, Aggregation> aggregationMap = goodsPage.getAggregations().asMap();
        for (Map.Entry<String, Aggregation>  entry : aggregationMap.entrySet()){
            //收集聚合查询的结果集
            Map<String,Object> map = new HashMap<>();
            //放入规格参数名
            map.put("k",entry.getKey());
            //收集规格参数值
            List<Object> options = new ArrayList<>();
            //解析每个聚合
            StringTerms  terms = (StringTerms)entry.getValue();
            //遍历每个聚合中的桶，把桶中key放入收集规格参数的集合中
            terms.getBuckets().forEach(bucket -> options.add(bucket.getKeyAsString()));
            map.put("options", options);
            paramMapList.add(map);
        }

        return paramMapList;
    }

    //构建bool查询构建器
    private BoolQueryBuilder buildBooleanQueryBuilder(SearchRequest request){

        BoolQueryBuilder boolQueryBuilder  = QueryBuilders.boolQuery();

        //添加基本查询条件
        //AND代表精确查询
        boolQueryBuilder.
                must(QueryBuilders.matchQuery("all",request.getKey()).operator(Operator.AND));

        //添加过滤条件

        //判空
        if (CollectionUtils.isEmpty(request.getFilters())){
            return boolQueryBuilder;
        }

        for (Map.Entry<String, String> entry : request.getFilters().entrySet()){
            String key = entry.getKey();
            //如果过滤条件是“品牌”，即用户选择了手机的品牌，转换伟过滤的字段名：brandId
            if (StringUtils.equals("品牌",key)){
                key = "brandId";
            }else if (StringUtils.equals("分类",key)){
                key = "cid3";
            }else {
                //如果是规格参数名 -> specs.key.keyword
                key = "specs." + key + ".keyword";
            }
            //去es索引库中查询
            boolQueryBuilder.filter(QueryBuilders.termQuery(key,entry.getValue()));
        }
        return boolQueryBuilder;
    }

    //创建索引
    public void crateIndex(Long id) throws IOException {
        Spu spu = this.goodsClient.querySpuById(id);
        //构建商品
        Goods goods = this.buildGoods(spu);
        //保存到索引库
        this.goodsRepository.save(goods);
    }

    //删除索引
    public void delete(Long id){
        this.goodsRepository.deleteById(id);
    }

}
